/*
 * Copyright (c) 2010, Michael Hengler (developer (at) hengler (dot) de),
 * Nikolaus Moll (developer (at) miblounge (dot) net).
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * * Neither the name of the Jombi project nor the
 * names of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL JOMBI BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */
package net.miblounge.jombi.impl.widgets;

import net.miblounge.jombi.impl.util.ColorConvert;
import net.miblounge.jombi.impl.util.CursorConvert;
import net.miblounge.jombi.impl.util.MouseUtil;
import net.miblounge.jombi.impl.util.PositionConvert;
import net.miblounge.jombi.impl.util.StyleSheetUtil;
import net.miblounge.jombi.impl.widgets.events.LazyKeyEventContentFactory;

import org.jowidgets.common.color.IColorConstant;
import org.jowidgets.common.types.Cursor;
import org.jowidgets.common.types.Dimension;
import org.jowidgets.common.types.MouseButton;
import org.jowidgets.common.types.Position;
import org.jowidgets.common.widgets.controller.IComponentListener;
import org.jowidgets.common.widgets.controller.IFocusListener;
import org.jowidgets.common.widgets.controller.IKeyListener;
import org.jowidgets.common.widgets.controller.IMouseListener;
import org.jowidgets.common.widgets.controller.IPopupDetectionListener;
import org.jowidgets.spi.impl.controller.ComponentObservable;
import org.jowidgets.spi.impl.controller.FocusObservable;
import org.jowidgets.spi.impl.controller.IObservableCallback;
import org.jowidgets.spi.impl.controller.KeyObservable;
import org.jowidgets.spi.impl.controller.MouseButtonEvent;
import org.jowidgets.spi.impl.controller.MouseObservable;
import org.jowidgets.spi.impl.controller.PopupDetectionObservable;
import org.jowidgets.spi.widgets.IComponentSpi;
import org.jowidgets.spi.widgets.IPopupMenuSpi;

import com.trolltech.qt.core.QEvent;
import com.trolltech.qt.core.QObject;
import com.trolltech.qt.core.QSize;
import com.trolltech.qt.gui.QColor;
import com.trolltech.qt.gui.QContextMenuEvent;
import com.trolltech.qt.gui.QCursor;
import com.trolltech.qt.gui.QKeyEvent;
import com.trolltech.qt.gui.QMouseEvent;
import com.trolltech.qt.gui.QPalette;
import com.trolltech.qt.gui.QPalette.ColorRole;
import com.trolltech.qt.gui.QWidget;

public class QtComponent implements IComponentSpi {

	private final QWidget component;
	private final PopupDetectionObservable popupDetectionObservable;
	private final FocusObservable focusObservable;
	private final KeyObservable keyObservable;
	private final MouseObservable mouseObservable;
	private final ComponentObservable componentObservable;

	private ColorRole foregroundColorRole = ColorRole.WindowText;

	//private ColorRole backgroundColorRole;

	private boolean keyEventsEnabled;

	public QtComponent(final QWidget component) {
		super();
		this.component = component;

		this.popupDetectionObservable = new PopupDetectionObservable();
		this.focusObservable = new FocusObservable();
		this.mouseObservable = new MouseObservable();
		this.componentObservable = new ComponentObservable();

		keyEventsEnabled = false;

		getUiReference().installEventFilter(new QObject() {
			@Override
			public boolean eventFilter(final QObject sender, final QEvent event) {
				if (event.type() == QEvent.Type.ContextMenu) {
					final QContextMenuEvent menuEvent = (QContextMenuEvent) event;
					menuEvent.ignore();
					popupDetectionObservable.firePopupDetected(new Position(menuEvent.x(), menuEvent.y()));
					return true;
				}
				if (event.type() == QEvent.Type.FocusIn) {
					// final QFocusEvent focusEvent = (QFocusEvent) event;
					focusObservable.focusGained();
				}
				if (event.type() == QEvent.Type.FocusOut) {
					// final QFocusEvent focusEvent = (QFocusEvent) event;
					focusObservable.focusLost();
				}

				if (keyEventsEnabled) {
					if (event.type() == QEvent.Type.KeyPress) {
						final QKeyEvent keyEvent = (QKeyEvent) event;
						keyObservable.fireKeyPressed(new LazyKeyEventContentFactory(keyEvent));
					}
					if (event.type() == QEvent.Type.KeyRelease) {
						final QKeyEvent keyEvent = (QKeyEvent) event;
						keyObservable.fireKeyReleased(new LazyKeyEventContentFactory(keyEvent));
					}
				}

				if (event instanceof QMouseEvent) {
					final QMouseEvent mouseEvent = (QMouseEvent) event;
					final MouseButtonEvent mouseButtonEvent = getMouseEvent(mouseEvent);

					if ((getUiReference().childAt(mouseEvent.pos()) == null) && (mouseButtonEvent != null)) {
						if (event.type() == QEvent.Type.MouseButtonPress) {
							mouseObservable.fireMousePressed(mouseButtonEvent);
						}
						if (event.type() == QEvent.Type.MouseButtonRelease) {
							mouseObservable.fireMouseReleased(mouseButtonEvent);
						}
						if (event.type() == QEvent.Type.MouseButtonDblClick) {
							mouseObservable.fireMouseDoubleClicked(mouseButtonEvent);
						}
					}
				}

				if (event.type() == QEvent.Type.Enter) {
					mouseObservable.fireMouseEnter(PositionConvert.convert(getUiReference().mapFromGlobal(QCursor.pos())));
				}

				if (event.type() == QEvent.Type.Leave) {
					mouseObservable.fireMouseExit(PositionConvert.convert(getUiReference().mapFromGlobal(QCursor.pos())));
				}

				if (event.type() == QEvent.Type.Resize) {
					componentObservable.fireSizeChanged();
				}
				if (event.type() == QEvent.Type.Move) {
					componentObservable.firePositionChanged();
				}

				return super.eventFilter(sender, event);
			}

			private MouseButtonEvent getMouseEvent(final QMouseEvent event) {
				final MouseButton mouseButton = MouseUtil.getMouseButton(event);
				if (mouseButton == null) {
					return null;
				}
				final Position position = new Position(event.x(), event.y());
				return new MouseButtonEvent(position, mouseButton, MouseUtil.getModifier(event.modifiers())) {
					@Override
					public String toString() {
						return super.toString() + " [" + getUiReference() + "@" + getUiReference().hashCode() + "]";
					}
				};
			}

		});

		final IObservableCallback keyObservableCallback = new IObservableCallback() {
			@Override
			public void onLastUnregistered() {
				keyEventsEnabled = false;
			}

			@Override
			public void onFirstRegistered() {
				keyEventsEnabled = true;
			}
		};

		this.keyObservable = new KeyObservable(keyObservableCallback);
	}

	@Override
	public QWidget getUiReference() {
		return component;
	}

	@Override
	public void redraw() {
		// TODO: improve ?
		component.repaint();
	}

	@Override
	public void setForegroundColor(final IColorConstant colorValue) {
		final QPalette palette = component.palette();
		palette.setColor(foregroundColorRole, ColorConvert.convert(colorValue));
		component.setPalette(palette);
	}

	@Override
	public IColorConstant getForegroundColor() {
		final QColor color = component.palette().color(foregroundColorRole);

		return ColorConvert.convert(color);
	}

	@Override
	public void setVisible(final boolean visible) {
		component.setVisible(visible);
	}

	@Override
	public boolean isVisible() {
		return component.isVisible();
	}

	// Qt fix
	@Override
	public void setBackgroundColor(final IColorConstant color) {
		final QPalette palette = component.palette();
		palette.setColor(ColorRole.Window, ColorConvert.convert(color));
		//palette.base().setColor(ColorConvert.convert(color));
		component.setPalette(palette);

		//		// fore some reason, foreground color will be reset after style changes
		//		// TODO: check if complete palette is reset
		//		final IColorConstant fg = getForegroundColor();
		//		StyleSheetInvoker.addStyleSheet(getUiReference(), "background-color", ColorConvert.convert(color).name());
		//		setForegroundColor(fg);
	}

	@Override
	public IColorConstant getBackgroundColor() {
		final QColor backgroundColor;

		final String colorString = StyleSheetUtil.getAttribute(component.styleSheet(), "background-color");
		if ((colorString == null) || (colorString.equals(""))) {
			// get color on other way
			backgroundColor = component.palette().base().color();
		} else {
			backgroundColor = new QColor(colorString);
		}
		return ColorConvert.convert(backgroundColor);
	}

	@Override
	public Dimension getSize() {
		final QSize size = component.size();
		return new Dimension(size.width(), size.height());
	}

	public void setForegroundColorRole(final ColorRole foregroundColorRole) {
		this.foregroundColorRole = foregroundColorRole;
	}

	@Override
	public void setCursor(final Cursor cursor) {
		component.setCursor(CursorConvert.convert(cursor));
	}

	@Override
	public void setEnabled(final boolean enabled) {
		component.setEnabled(enabled);
	}

	@Override
	public boolean isEnabled() {
		return component.isEnabled();
	}

	@Override
	public void addPopupDetectionListener(final IPopupDetectionListener listener) {
		popupDetectionObservable.addPopupDetectionListener(listener);
	}

	@Override
	public void removePopupDetectionListener(final IPopupDetectionListener listener) {
		popupDetectionObservable.removePopupDetectionListener(listener);
	}

	protected PopupDetectionObservable getPopupDetectionObservable() {
		return popupDetectionObservable;
	}

	@Override
	public IPopupMenuSpi createPopupMenu() {
		return new PopupMenuImpl(getUiReference());
	}

	//	public void setBackgroundColorRole(final ColorRole backgroundColorRole) {
	//		this.backgroundColorRole = backgroundColorRole;
	//	}

	@Override
	public Position getPosition() {
		return PositionConvert.convert(component.pos());
	}

	@Override
	public void setPosition(final Position position) {
		System.out.println("Setting position (" + getUiReference() + "): " + position);
		final Dimension size = getSize();
		component.setGeometry(position.getX(), position.getY(), size.getWidth(), size.getHeight());
	}

	@Override
	public void setSize(final Dimension size) {
		System.out.println("Setting size (" + getUiReference() + "): " + size);
		final Position position = getPosition();
		component.setGeometry(position.getX(), position.getY(), size.getWidth(), size.getHeight());
	}

	@Override
	public void setRedrawEnabled(final boolean enabled) {
		component.setUpdatesEnabled(enabled);
	}

	@Override
	public boolean requestFocus() {
		getUiReference().setFocus();

		// TODO NM check if working
		return getUiReference().hasFocus();
	}

	@Override
	public void addFocusListener(final IFocusListener listener) {
		focusObservable.addFocusListener(listener);
	}

	@Override
	public void removeFocusListener(final IFocusListener listener) {
		focusObservable.removeFocusListener(listener);
	}

	@Override
	public void addKeyListener(final IKeyListener listener) {
		keyObservable.addKeyListener(listener);
	}

	@Override
	public void removeKeyListener(final IKeyListener listener) {
		keyObservable.removeKeyListener(listener);
	}

	@Override
	public void addComponentListener(final IComponentListener componentListener) {
		componentObservable.addComponentListener(componentListener);
	}

	@Override
	public void removeComponentListener(final IComponentListener componentListener) {
		componentObservable.removeComponentListener(componentListener);
	}

	@Override
	public void addMouseListener(final IMouseListener mouseListener) {
		mouseObservable.addMouseListener(mouseListener);
	}

	@Override
	public void removeMouseListener(final IMouseListener mouseListener) {
		mouseObservable.removeMouseListener(mouseListener);
	}
}
